/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.nutch.plugin;

import java.util.HashMap;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configurable;

/**
 * An <code>Extension</code> is a kind of listener descriptor that will be
 * installed on a concrete <code>ExtensionPoint</code> that acts as kind of
 * Publisher.
 */
public class Extension {
	private PluginDescriptor fDescriptor;
	private String fId;
	private String fTargetPoint;
	private String fClazz;
	private HashMap<String, String> fAttributes;
	private Configuration conf;

	/**
	 * @param pDescriptor
	 *            a plugin descriptor
	 * @param pExtensionPoint
	 *            an extension porin
	 * @param pId
	 *            an unique id of the plugin
	 */
	public Extension(PluginDescriptor pDescriptor, String pExtensionPoint,
			String pId, String pExtensionClass, Configuration conf,
			PluginRepository pluginRepository) {
		fAttributes = new HashMap<String, String>();
		setDescriptor(pDescriptor);
		setExtensionPoint(pExtensionPoint);
		setId(pId);
		setClazz(pExtensionClass);
		this.conf = conf;
	}

	/**
	 * @param point
	 */
	private void setExtensionPoint(String point) {
		fTargetPoint = point;
	}

	/**
	 * Returns a attribute value, that is setuped in the manifest file and is
	 * definied by the extension point xml schema.
	 * 
	 * @param pKey
	 *            a key
	 * @return String a value
	 */
	public String getAttribute(String pKey) {
		return fAttributes.get(pKey);
	}

	/**
	 * Returns the full class name of the extension point implementation
	 * 
	 * @return String
	 */
	public String getClazz() {
		return fClazz;
	}

	/**
	 * Return the unique id of the extension.
	 * 
	 * @return String
	 */
	public String getId() {
		return fId;
	}

	/**
	 * Adds a attribute and is only used until model creation at plugin system
	 * start up.
	 * 
	 * @param pKey
	 *            a key
	 * @param pValue
	 *            a value
	 */
	public void addAttribute(String pKey, String pValue) {
		fAttributes.put(pKey, pValue);
	}

	/**
	 * Sets the Class that implement the concret extension and is only used
	 * until model creation at system start up.
	 * 
	 * @param extensionClazz
	 *            The extensionClasname to set
	 */
	public void setClazz(String extensionClazz) {
		fClazz = extensionClazz;
	}

	/**
	 * Sets the unique extension Id and is only used until model creation at
	 * system start up.
	 * 
	 * @param extensionID
	 *            The extensionID to set
	 */
	public void setId(String extensionID) {
		fId = extensionID;
	}

	/**
	 * Returns the Id of the extension point, that is implemented by this
	 * extension.
	 */
	public String getTargetPoint() {
		return fTargetPoint;
	}

	/**
	 * Return an instance of the extension implementatio. Before we create a
	 * extension instance we startup the plugin if it is not already done. The
	 * plugin instance and the extension instance use the same
	 * <code>PluginClassLoader</code>. Each Plugin use its own classloader. The
	 * PluginClassLoader knows only own <i>Plugin runtime libraries </i> setuped
	 * in the plugin manifest file and exported libraries of the depenedend
	 * plugins.
	 * 
	 * @return Object An instance of the extension implementation
	 */
	public Object getExtensionInstance() throws PluginRuntimeException {
		// Must synchronize here to make sure creation and initialization
		// of a plugin instance and it extension instance are done by
		// one and only one thread.
		// The same is in PluginRepository.getPluginInstance().
		// Suggested by Stefan Groschupf <sg@media-style.com>
		synchronized (getId()) {
			try {
				PluginRepository pluginRepository = PluginRepository.get(conf);
				Class extensionClazz = pluginRepository
						.getCachedClass(fDescriptor, getClazz());
				// lazy loading of Plugin in case there is no instance of the
				// plugin
				// already.
				pluginRepository.getPluginInstance(getDescriptor());
				Object object = extensionClazz.newInstance();
				if (object instanceof Configurable) {
					((Configurable) object).setConf(this.conf);
				}
				return object;
			} catch (ClassNotFoundException e) {
				throw new PluginRuntimeException(e);
			} catch (InstantiationException e) {
				throw new PluginRuntimeException(e);
			} catch (IllegalAccessException e) {
				throw new PluginRuntimeException(e);
			}
		}
	}

	/**
	 * return the plugin descriptor.
	 * 
	 * @return PluginDescriptor
	 */
	public PluginDescriptor getDescriptor() {
		return fDescriptor;
	}

	/**
	 * Sets the plugin descriptor and is only used until model creation at
	 * system start up.
	 * 
	 * @param pDescriptor
	 */
	public void setDescriptor(PluginDescriptor pDescriptor) {
		fDescriptor = pDescriptor;
	}
}
